/******************************************************************************
 * Spine Runtimes Software License
 * Version 2.1
 * 
 * Copyright (c) 2013, Esoteric Software
 * All rights reserved.
 * 
 * You are granted a perpetual, non-exclusive, non-sublicensable and
 * non-transferable license to install, execute and perform the Spine Runtimes
 * Software (the "Software") solely for internal use. Without the written
 * permission of Esoteric Software (typically granted by licensing Spine), you
 * may not (a) modify, translate, adapt or otherwise create derivative works,
 * improvements of the Software or develop new applications using the Software
 * or (b) remove, delete, alter or obscure any trademarks or any copyright,
 * trademark, patent or other intellectual property or proprietary rights
 * notices on or in the Software, including any copy thereof. Redistributions
 * in binary or source form must include this license and terms.
 * 
 * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

/*****************************************************************************
 * Automatic import and advanced preview added by Mitch Thompson
 * Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using System;
using System.Collections.Generic;
using UnityEditor;

#if !UNITY_4_3
using UnityEditor.AnimatedValues;
#endif
using UnityEngine;
using Spine;

[CustomEditor(typeof(SkeletonDataAsset))]
public class SkeletonDataAssetInspector : Editor {
	static bool showAnimationStateData = true;
	static bool showAnimationList = true;
	static bool showSlotList = false;
	static bool showAttachments = false;
	static bool showBaking = true;
	static bool bakeAnimations = true;
	static bool bakeIK = true;
	static SendMessageOptions bakeEventOptions = SendMessageOptions.DontRequireReceiver;

	private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix, controller;

#if SPINE_TK2D
	private SerializedProperty spriteCollection;
#endif

	private bool m_initialized = false;
	private SkeletonDataAsset m_skeletonDataAsset;
	private SkeletonData m_skeletonData;
	private string m_skeletonDataAssetGUID;
	private bool needToSerialize;

	List<string> warnings = new List<string>();
	
	void OnEnable () {

		SpineEditorUtilities.ConfirmInitialization();

		try {
			atlasAssets = serializedObject.FindProperty("atlasAssets");
			skeletonJSON = serializedObject.FindProperty("skeletonJSON");
			scale = serializedObject.FindProperty("scale");
			fromAnimation = serializedObject.FindProperty("fromAnimation");
			toAnimation = serializedObject.FindProperty("toAnimation");
			duration = serializedObject.FindProperty("duration");
			defaultMix = serializedObject.FindProperty("defaultMix");
			controller = serializedObject.FindProperty("controller");
#if SPINE_TK2D
			spriteCollection = serializedObject.FindProperty("spriteCollection");
#endif

			m_skeletonDataAsset = (SkeletonDataAsset)target;
			m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));

			EditorApplication.update += Update;
		} catch {


		}

		m_skeletonData = m_skeletonDataAsset.GetSkeletonData(true);

		showBaking = EditorPrefs.GetBool("SkeletonDataAssetInspector_showBaking", true);

		RepopulateWarnings();
	}

	void OnDestroy () {
		m_initialized = false;
		EditorApplication.update -= Update;
		this.DestroyPreviewInstances();
		if (this.m_previewUtility != null) {
			this.m_previewUtility.Cleanup();
			this.m_previewUtility = null;
		}
	}

	override public void OnInspectorGUI () {
		serializedObject.Update();

		EditorGUI.BeginChangeCheck();
#if !SPINE_TK2D
		EditorGUILayout.PropertyField(atlasAssets, true);
#else
		EditorGUI.BeginDisabledGroup(spriteCollection.objectReferenceValue != null);
		EditorGUILayout.PropertyField(atlasAssets, true);
		EditorGUI.EndDisabledGroup();
		EditorGUILayout.PropertyField(spriteCollection, true);
#endif
		EditorGUILayout.PropertyField(skeletonJSON);
		EditorGUILayout.PropertyField(scale);
		if (EditorGUI.EndChangeCheck()) {
			if (serializedObject.ApplyModifiedProperties()) {

				if (m_previewUtility != null) {
					m_previewUtility.Cleanup();
					m_previewUtility = null;
				}

				RepopulateWarnings();
				OnEnable();
				return;
			}

		}


		if (m_skeletonData != null) {
			DrawMecanim();
			DrawAnimationStateInfo();
			DrawAnimationList();
			DrawSlotList();
			DrawBaking();
			
		} else {

			DrawReimportButton();
			//Show Warnings
			foreach (var str in warnings)
				EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning));
		}

		if(!Application.isPlaying)
			serializedObject.ApplyModifiedProperties();
	}

	void DrawMecanim () {
		EditorGUILayout.PropertyField(controller, new GUIContent("Controller", SpineEditorUtilities.Icons.controllerIcon));		
		if (controller.objectReferenceValue == null) {
			if (GUILayout.Button(new GUIContent("Generate Mecanim Controller", SpineEditorUtilities.Icons.controllerIcon), GUILayout.Width(195), GUILayout.Height(20)))
				SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);
		}
	}

	void DrawBaking () {
		bool pre = showBaking;
		showBaking = EditorGUILayout.Foldout(showBaking, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon));
		if (pre != showBaking)
			EditorPrefs.SetBool("SkeletonDataAssetInspector_showBaking", showBaking);

		if (showBaking) {
			EditorGUI.indentLevel++;
			bakeAnimations = EditorGUILayout.Toggle("Bake Animations", bakeAnimations);
			EditorGUI.BeginDisabledGroup(bakeAnimations == false);
			{
				EditorGUI.indentLevel++;
				bakeIK = EditorGUILayout.Toggle("Bake IK", bakeIK);
				bakeEventOptions = (SendMessageOptions)EditorGUILayout.EnumPopup("Event Options", bakeEventOptions);
				EditorGUI.indentLevel--;
			}
			EditorGUI.EndDisabledGroup();

			EditorGUI.indentLevel++;
			GUILayout.BeginHorizontal();
			{


				if (GUILayout.Button(new GUIContent("Bake All Skins", SpineEditorUtilities.Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(150)))
					SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, m_skeletonData.Skins, "", bakeAnimations, bakeIK, bakeEventOptions);

				string skinName = "<No Skin>";

				if (m_skeletonAnimation != null && m_skeletonAnimation.skeleton != null) {

					Skin bakeSkin = m_skeletonAnimation.skeleton.Skin;
					if (bakeSkin == null) {
						skinName = "Default";
						bakeSkin = m_skeletonData.Skins[0];
					} else
						skinName = m_skeletonAnimation.skeleton.Skin.Name;

					bool oops = false;

					try {
						GUILayout.BeginVertical();
						if (GUILayout.Button(new GUIContent("Bake " + skinName, SpineEditorUtilities.Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(250)))
							SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, new List<Skin>(new Skin[] { bakeSkin }), "", bakeAnimations, bakeIK, bakeEventOptions);

						GUILayout.BeginHorizontal();
						GUILayout.Label(new GUIContent("Skins", SpineEditorUtilities.Icons.skinsRoot), GUILayout.Width(50));
						if (GUILayout.Button(skinName, EditorStyles.popup, GUILayout.Width(196))) {
							SelectSkinContext();
						}
						GUILayout.EndHorizontal();



					} catch {
						oops = true;
						//GUILayout.BeginVertical();
					}



					if (!oops)
						GUILayout.EndVertical();
				}

			}
			GUILayout.EndHorizontal();
			EditorGUI.indentLevel--;
			EditorGUI.indentLevel--;
		}


	}
	void DrawReimportButton () {
		EditorGUI.BeginDisabledGroup(skeletonJSON.objectReferenceValue == null);
		if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) {
			DoReimport();
			return;
		}
		EditorGUI.EndDisabledGroup();
	}

	void DoReimport () {
		SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);

		if (m_previewUtility != null) {
			m_previewUtility.Cleanup();
			m_previewUtility = null;
		}

		RepopulateWarnings();
		OnEnable();

		EditorUtility.SetDirty(m_skeletonDataAsset);
	}

	void DrawAnimationStateInfo () {
		showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
		if (!showAnimationStateData)
			return;

		EditorGUI.BeginChangeCheck();
		EditorGUILayout.PropertyField(defaultMix);

		// Animation names
		String[] animations = new String[m_skeletonData.Animations.Count];
		for (int i = 0; i < animations.Length; i++)
			animations[i] = m_skeletonData.Animations[i].Name;

		for (int i = 0; i < fromAnimation.arraySize; i++) {
			SerializedProperty from = fromAnimation.GetArrayElementAtIndex(i);
			SerializedProperty to = toAnimation.GetArrayElementAtIndex(i);
			SerializedProperty durationProp = duration.GetArrayElementAtIndex(i);
			EditorGUILayout.BeginHorizontal();
			from.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, from.stringValue), 0), animations)];
			to.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, to.stringValue), 0), animations)];
			durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue);
			if (GUILayout.Button("Delete")) {
				duration.DeleteArrayElementAtIndex(i);
				toAnimation.DeleteArrayElementAtIndex(i);
				fromAnimation.DeleteArrayElementAtIndex(i);
			}
			EditorGUILayout.EndHorizontal();
		}
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.Space();
		if (GUILayout.Button("Add Mix")) {
			duration.arraySize++;
			toAnimation.arraySize++;
			fromAnimation.arraySize++;
		}
		EditorGUILayout.Space();
		EditorGUILayout.EndHorizontal();

		if (EditorGUI.EndChangeCheck()) {
			m_skeletonDataAsset.FillStateData();
			EditorUtility.SetDirty(m_skeletonDataAsset);
			serializedObject.ApplyModifiedProperties();
			needToSerialize = true;
		}
			
	}
	void DrawAnimationList () {
		showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
		if (!showAnimationList)
			return;

		if (GUILayout.Button(new GUIContent("Setup Pose", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
			StopAnimation();
			m_skeletonAnimation.skeleton.SetToSetupPose();
			m_requireRefresh = true;
		}

		EditorGUILayout.LabelField("Name", "Duration");
		foreach (Spine.Animation a in m_skeletonData.Animations) {
			GUILayout.BeginHorizontal();

			if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
				if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) {
					GUI.contentColor = Color.black;
					if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
						StopAnimation();
					}
					GUI.contentColor = Color.white;
				} else {
					if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
						PlayAnimation(a.Name, true);
					}
				}
			} else {
				GUILayout.Label("?", GUILayout.Width(24));
			}
			EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' ')));
			GUILayout.EndHorizontal();
		}
	}


	void DrawSlotList () {
		showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot));

		if (!showSlotList)
			return;

		if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null)
			return;

		EditorGUI.indentLevel++;
		try {
			showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
		} catch {
			return;
		}


		List<Attachment> slotAttachments = new List<Attachment>();
		List<string> slotAttachmentNames = new List<string>();
		List<string> defaultSkinAttachmentNames = new List<string>();
		var defaultSkin = m_skeletonData.Skins[0];
		Skin skin = m_skeletonAnimation.skeleton.Skin;
		if (skin == null) {
			skin = defaultSkin;
		}

		for (int i = m_skeletonAnimation.skeleton.Slots.Count - 1; i >= 0; i--) {
			Slot slot = m_skeletonAnimation.skeleton.Slots[i];
			EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot));
			if (showAttachments) {


				EditorGUI.indentLevel++;
				slotAttachments.Clear();
				slotAttachmentNames.Clear();
				defaultSkinAttachmentNames.Clear();

				skin.FindNamesForSlot(i, slotAttachmentNames);
				skin.FindAttachmentsForSlot(i, slotAttachments);


				if (skin != defaultSkin) {
					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
					defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
					defaultSkin.FindAttachmentsForSlot(i, slotAttachments);
				} else {
					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
				}



				for (int a = 0; a < slotAttachments.Count; a++) {
					Attachment attachment = slotAttachments[a];
					string name = slotAttachmentNames[a];

					Texture2D icon = null;
					var type = attachment.GetType();

					if (type == typeof(RegionAttachment))
						icon = SpineEditorUtilities.Icons.image;
					else if (type == typeof(MeshAttachment))
						icon = SpineEditorUtilities.Icons.mesh;
					else if (type == typeof(BoundingBoxAttachment))
						icon = SpineEditorUtilities.Icons.boundingBox;
					else if (type == typeof(SkinnedMeshAttachment))
						icon = SpineEditorUtilities.Icons.weights;
					else
						icon = SpineEditorUtilities.Icons.warning;

					//TODO:  Waterboard Nate
					//if (name != attachment.Name)
					//icon = SpineEditorUtilities.Icons.skinPlaceholder;

					bool initialState = slot.Attachment == attachment;

					bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment);

					if (!defaultSkinAttachmentNames.Contains(name)) {
						Rect skinPlaceHolderIconRect = GUILayoutUtility.GetLastRect();
						skinPlaceHolderIconRect.width = SpineEditorUtilities.Icons.skinPlaceholder.width;
						skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height;
						GUI.DrawTexture(skinPlaceHolderIconRect, SpineEditorUtilities.Icons.skinPlaceholder);
					}


					if (toggled != initialState) {
						if (toggled) {
							slot.Attachment = attachment;
						} else {
							slot.Attachment = null;
						}
						m_requireRefresh = true;
					}
				}


				EditorGUI.indentLevel--;
			}
		}

		EditorGUI.indentLevel--;
	}


	void RepopulateWarnings () {
		warnings.Clear();

		if (skeletonJSON.objectReferenceValue == null)
			warnings.Add("Missing Skeleton JSON");
		else {

			if (SpineEditorUtilities.IsValidSpineData((TextAsset)skeletonJSON.objectReferenceValue) == false) {
				warnings.Add("Skeleton data file is not a valid JSON or binary file.");
			} else {
				bool detectedNullAtlasEntry = false;
				List<Atlas> atlasList = new List<Atlas>();
				for (int i = 0; i < atlasAssets.arraySize; i++) {
					if (atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue == null) {
						detectedNullAtlasEntry = true;
						break;
					} else {
						atlasList.Add(((AtlasAsset)atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue).GetAtlas());
					}
				}

				if (detectedNullAtlasEntry)
					warnings.Add("AtlasAsset elements cannot be Null");
				else {
					//get requirements
					var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));

					foreach (var atlas in atlasList) {
						for (int i = 0; i < missingPaths.Count; i++) {
							if (atlas.FindRegion(missingPaths[i]) != null) {
								missingPaths.RemoveAt(i);
								i--;
							}
						}
					}

					foreach (var str in missingPaths)
						warnings.Add("Missing Region: '" + str + "'");



				}
			}


		}
	}

	//preview window stuff
	private PreviewRenderUtility m_previewUtility;
	private GameObject m_previewInstance;
	private Vector2 previewDir;
	private SkeletonAnimation m_skeletonAnimation;
	//private SkeletonData m_skeletonData;
	private static int sliderHash = "Slider".GetHashCode();
	private float m_lastTime;
	private bool m_playing;
	private bool m_requireRefresh;
	private Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1);

	private void StopAnimation () {
		m_skeletonAnimation.state.ClearTrack(0);
		m_playing = false;
	}

	List<Spine.Event> m_animEvents = new List<Spine.Event>();
	List<float> m_animEventFrames = new List<float>();

	private void PlayAnimation (string animName, bool loop) {
		m_animEvents.Clear();
		m_animEventFrames.Clear();

		m_skeletonAnimation.state.SetAnimation(0, animName, loop);

		Spine.Animation a = m_skeletonAnimation.state.GetCurrent(0).Animation;
		foreach (Timeline t in a.Timelines) {
			if (t.GetType() == typeof(EventTimeline)) {
				EventTimeline et = (EventTimeline)t;

				for (int i = 0; i < et.Events.Length; i++) {
					m_animEvents.Add(et.Events[i]);
					m_animEventFrames.Add(et.Frames[i]);
				}

			}
		}

		m_playing = true;
	}

	private void InitPreview () {
		if (this.m_previewUtility == null) {
			this.m_lastTime = Time.realtimeSinceStartup;
			this.m_previewUtility = new PreviewRenderUtility(true);
			this.m_previewUtility.m_Camera.orthographic = true;
			this.m_previewUtility.m_Camera.orthographicSize = 1;
			this.m_previewUtility.m_Camera.cullingMask = -2147483648;
			this.m_previewUtility.m_Camera.nearClipPlane = 0.01f;
			this.m_previewUtility.m_Camera.farClipPlane = 1000f;
			this.CreatePreviewInstances();
		}
	}

	private void CreatePreviewInstances () {
		this.DestroyPreviewInstances();
		if (this.m_previewInstance == null) {
			try {
				string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");

				m_previewInstance = SpineEditorUtilities.InstantiateSkeletonAnimation((SkeletonDataAsset)target, skinName).gameObject;
				m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
				m_previewInstance.layer = 0x1f;


				m_skeletonAnimation = m_previewInstance.GetComponent<SkeletonAnimation>();
				m_skeletonAnimation.initialSkinName = skinName;
				m_skeletonAnimation.LateUpdate();

				m_skeletonData = m_skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);

				m_previewInstance.GetComponent<Renderer>().enabled = false;

				m_initialized = true;
				AdjustCameraGoals(true);
			} catch {

			}
		}
	}

	private void DestroyPreviewInstances () {
		if (this.m_previewInstance != null) {
			DestroyImmediate(this.m_previewInstance);
			m_previewInstance = null;
		}
		m_initialized = false;
	}

	public override bool HasPreviewGUI () {
		//TODO: validate json data

		for (int i = 0; i < atlasAssets.arraySize; i++) {
			var prop = atlasAssets.GetArrayElementAtIndex(i);
			if (prop.objectReferenceValue == null)
				return false;
		}

		return skeletonJSON.objectReferenceValue != null;
	}

	Texture m_previewTex = new Texture();

	public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
		this.InitPreview();

		if (UnityEngine.Event.current.type == EventType.Repaint) {
			if (m_requireRefresh) {
				this.m_previewUtility.BeginPreview(r, background);
				this.DoRenderPreview(true);
				this.m_previewTex = this.m_previewUtility.EndPreview();
				m_requireRefresh = false;
			}
			if (this.m_previewTex != null)
				GUI.DrawTexture(r, m_previewTex, ScaleMode.StretchToFill, false);
		}

		DrawSkinToolbar(r);
		NormalizedTimeBar(r);
		//TODO: implement panning
		//		this.previewDir = Drag2D(this.previewDir, r);
		MouseScroll(r);
	}

	float m_orthoGoal = 1;
	Vector3 m_posGoal = new Vector3(0, 0, -10);
	double m_adjustFrameEndTime = 0;

	private void AdjustCameraGoals (bool calculateMixTime) {
		if (this.m_previewInstance == null)
			return;

		if (calculateMixTime) {
			if (m_skeletonAnimation.state.GetCurrent(0) != null) {
				m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix;
			}
		}


		GameObject go = this.m_previewInstance;

		Bounds bounds = go.GetComponent<Renderer>().bounds;
		m_orthoGoal = bounds.size.y;

		m_posGoal = bounds.center + new Vector3(0, 0, -10);
	}

	private void AdjustCameraGoals () {
		AdjustCameraGoals(false);
	}

	private void AdjustCamera () {
		if (m_previewUtility == null)
			return;


		if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) {
			AdjustCameraGoals();
		}

		float orthoSet = Mathf.Lerp(this.m_previewUtility.m_Camera.orthographicSize, m_orthoGoal, 0.1f);

		this.m_previewUtility.m_Camera.orthographicSize = orthoSet;

		float dist = Vector3.Distance(m_previewUtility.m_Camera.transform.position, m_posGoal);
		if (dist > 60f * ((SkeletonDataAsset)target).scale) {
			Vector3 pos = Vector3.Lerp(this.m_previewUtility.m_Camera.transform.position, m_posGoal, 0.1f);
			pos.x = 0;
			this.m_previewUtility.m_Camera.transform.position = pos;
			this.m_previewUtility.m_Camera.transform.rotation = Quaternion.identity;
			m_requireRefresh = true;
		}
	}

	private void DoRenderPreview (bool drawHandles) {
		GameObject go = this.m_previewInstance;

		if (m_requireRefresh && go != null) {
			go.GetComponent<Renderer>().enabled = true;

			if (EditorApplication.isPlaying) {
				//do nothing
			} else {
				m_skeletonAnimation.Update((Time.realtimeSinceStartup - m_lastTime));
			}

			m_lastTime = Time.realtimeSinceStartup;

			if (!EditorApplication.isPlaying)
				m_skeletonAnimation.LateUpdate();



			if (drawHandles) {			
				Handles.SetCamera(m_previewUtility.m_Camera);
				Handles.color = m_originColor;

				Handles.DrawLine(new Vector3(-1000 * m_skeletonDataAsset.scale, 0, 0), new Vector3(1000 * m_skeletonDataAsset.scale, 0, 0));
				Handles.DrawLine(new Vector3(0, 1000 * m_skeletonDataAsset.scale, 0), new Vector3(0, -1000 * m_skeletonDataAsset.scale, 0));
			}

			this.m_previewUtility.m_Camera.Render();

			if (drawHandles) {
				Handles.SetCamera(m_previewUtility.m_Camera);
				foreach (var slot in m_skeletonAnimation.skeleton.Slots) {
					if (slot.Attachment is BoundingBoxAttachment) {

						DrawBoundingBox(slot.Bone, (BoundingBoxAttachment)slot.Attachment);
					}
				}
			}

			go.GetComponent<Renderer>().enabled = false;
		}


	}

	void DrawBoundingBox (Bone bone, BoundingBoxAttachment box) {
		float[] worldVerts = new float[box.Vertices.Length];
		box.ComputeWorldVertices(bone, worldVerts);

		Handles.color = Color.green;
		Vector3 lastVert = Vector3.back;
		Vector3 vert = Vector3.back;
		Vector3 firstVert = new Vector3(worldVerts[0], worldVerts[1], -1);
		for (int i = 0; i < worldVerts.Length; i += 2) {
			vert.x = worldVerts[i];
			vert.y = worldVerts[i + 1];

			if (i > 0) {
				Handles.DrawLine(lastVert, vert);
			}


			lastVert = vert;
		}

		Handles.DrawLine(lastVert, firstVert);

		
		
	}

	void Update () {
		AdjustCamera();

		if (m_playing) {
			m_requireRefresh = true;
			Repaint();
		} else if (m_requireRefresh) {
			Repaint();
		} else {
			//only needed if using smooth menus
		}

		if (needToSerialize) {
			needToSerialize = false;
			serializedObject.ApplyModifiedProperties();
		}
	}

	void DrawSkinToolbar (Rect r) {
		if (m_skeletonAnimation == null)
			return;

		if (m_skeletonAnimation.skeleton != null) {
			string label = (m_skeletonAnimation.skeleton != null && m_skeletonAnimation.skeleton.Skin != null) ? m_skeletonAnimation.skeleton.Skin.Name : "default";

			Rect popRect = new Rect(r);
			popRect.y += 32;
			popRect.x += 4;
			popRect.height = 24;
			popRect.width = 40;
			EditorGUI.DropShadowLabel(popRect, new GUIContent("Skin", SpineEditorUtilities.Icons.skinsRoot));

			popRect.y += 11;
			popRect.width = 150;
			popRect.x += 44;

			if (GUI.Button(popRect, label, EditorStyles.popup)) {
				SelectSkinContext();
			}
		}
	}

	void SelectSkinContext () {
		GenericMenu menu = new GenericMenu();

		foreach (Skin s in m_skeletonData.Skins) {
			menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, (object)s);
		}

		menu.ShowAsContext();
	}

	void SetSkin (object o) {
		Skin skin = (Skin)o;

		m_skeletonAnimation.initialSkinName = skin.Name;
		m_skeletonAnimation.Reset();
		m_requireRefresh = true;

		EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
	}

	void NormalizedTimeBar (Rect r) {
		if (m_skeletonAnimation == null)
			return;

		Rect barRect = new Rect(r);
		barRect.height = 32;
		barRect.x += 4;
		barRect.width -= 4;

		GUI.Box(barRect, "");

		Rect lineRect = new Rect(barRect);
		float width = lineRect.width;
		TrackEntry t = m_skeletonAnimation.state.GetCurrent(0);

		if (t != null) {
			int loopCount = (int)(t.Time / t.EndTime);
			float currentTime = t.Time - (t.EndTime * loopCount);

			float normalizedTime = currentTime / t.Animation.Duration;

			lineRect.x = barRect.x + (width * normalizedTime) - 0.5f;
			lineRect.width = 2;

			GUI.color = Color.red;
			GUI.DrawTexture(lineRect, EditorGUIUtility.whiteTexture);
			GUI.color = Color.white;

			for (int i = 0; i < m_animEvents.Count; i++) {
				//TODO: Tooltip
				//Spine.Event spev = animEvents[i];

				float fr = m_animEventFrames[i];

				Rect evRect = new Rect(barRect);
				evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons._event.width / 2), barRect.x, float.MaxValue);
				evRect.width = SpineEditorUtilities.Icons._event.width;
				evRect.height = SpineEditorUtilities.Icons._event.height;
				evRect.y += SpineEditorUtilities.Icons._event.height;
				GUI.DrawTexture(evRect, SpineEditorUtilities.Icons._event);


				//TODO:  Tooltip
				/*
				UnityEngine.Event ev = UnityEngine.Event.current;
				if(ev.isMouse){
					if(evRect.Contains(ev.mousePosition)){
						Rect tooltipRect = new Rect(evRect);
						tooltipRect.width = 500;
						tooltipRect.y -= 4;
						tooltipRect.x += 4;
						GUI.Label(tooltipRect, spev.Data.Name);
					}
				}
				*/
			}
		}
	}

	void MouseScroll (Rect position) {
		UnityEngine.Event current = UnityEngine.Event.current;
		int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);

		switch (current.GetTypeForControl(controlID)) {
			case EventType.ScrollWheel:
				if (position.Contains(current.mousePosition)) {

					m_orthoGoal += current.delta.y * ((SkeletonDataAsset)target).scale * 10;
					GUIUtility.hotControl = controlID;
					current.Use();
				}
				break;
		}

	}

	//TODO:  Implement preview panning
	/*
	static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
	{
		int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
		UnityEngine.Event current = UnityEngine.Event.current;
		switch (current.GetTypeForControl(controlID))
		{
		case EventType.MouseDown:
			if (position.Contains(current.mousePosition) && (position.width > 50f))
			{
				GUIUtility.hotControl = controlID;
				current.Use();
				EditorGUIUtility.SetWantsMouseJumping(1);
			}
			return scrollPosition;
			
		case EventType.MouseUp:
			if (GUIUtility.hotControl == controlID)
			{
				GUIUtility.hotControl = 0;
			}
			EditorGUIUtility.SetWantsMouseJumping(0);
			return scrollPosition;
			
		case EventType.MouseMove:
			return scrollPosition;
			
		case EventType.MouseDrag:
			if (GUIUtility.hotControl == controlID)
			{
				scrollPosition -= (Vector2) (((current.delta * (!current.shift ? ((float) 1) : ((float) 3))) / Mathf.Min(position.width, position.height)) * 140f);
				scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90f, 90f);
				current.Use();
				GUI.changed = true;
			}
			return scrollPosition;
		}
		return scrollPosition;
	}
	*/

	public override GUIContent GetPreviewTitle () {
		return new GUIContent("Preview");
	}

	public override void OnPreviewSettings () {
		if (!m_initialized) {
			GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
		} else {
			float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(64));

			//snap to nearest 0.25
			float y = speed / 0.25f;
			int q = Mathf.RoundToInt(y);
			speed = q * 0.25f;

			m_skeletonAnimation.timeScale = speed;
		}
	}

	//TODO:  Fix first-import error
	//TODO:  Update preview without thumbnail
	public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
		Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false);

		this.InitPreview();

		if (this.m_previewUtility.m_Camera == null)
			return null;

		m_requireRefresh = true;
		this.DoRenderPreview(false);
		AdjustCameraGoals(false);

		this.m_previewUtility.m_Camera.orthographicSize = m_orthoGoal / 2;
		this.m_previewUtility.m_Camera.transform.position = m_posGoal;
		this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
		this.DoRenderPreview(false);

		//TODO:  Figure out why this is throwing errors on first attempt
		//		if(m_previewUtility != null){
		//			Handles.SetCamera(this.m_previewUtility.m_Camera);
		//			Handles.BeginGUI();
		//			GUI.DrawTexture(new Rect(40,60,width,height), SpineEditorUtilities.Icons.spine, ScaleMode.StretchToFill);
		//			Handles.EndGUI();
		//		}
		tex = this.m_previewUtility.EndStaticPreview();
		return tex;
	}
}